/* Copyright 2017, 2018, Gabriel Czernikier
 *
 * This file is part of Notehaus.
 * Notehaus is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * Notehaus is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with Notehaus.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <errno.h>
#include <iconv.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include "composer.h"
#include "java7.tab.h"
#include "lgwseq.h"
#include "parse_file.h"

static size_t filebufsize;
static wchar_t *filebuf;
static const wchar_t *ptr;
int
wtname_lookup(const wchar_t *ptr, size_t len);

static const wchar_t *lookup_words[][2] = {
 {L"byte", L"B"},
 {L"short", L"S"},
 {L"char", L"C"},
 {L"int", L"I"},
 {L"long", L"J"},
 {L"float", L"F"},
 {L"double", L"D"},
 {L"boolean", L"Z"},
 {NULL, NULL}
};

void
parse_file(const char *fname) {
  FILE *fin = fopen(fname, "rb");
  if(fin==NULL) {
    fprintf(stderr, "Problem openning file %s.", fname);
    exit(EXIT_FAILURE);
  }

  iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
  if(cd==(iconv_t)-1) {
    if(errno==EINVAL)
      fprintf(stderr, "conversion from UTF-8 to wchar_t not available.");
    else
      perror("iconv_open()");
    exit(EXIT_FAILURE);
  }
  size_t mblen = 0;
  wchar_t *outbuf = malloc(sizeof(wchar_t)); /* GNU malloc always arranges at 8-byte boundary,
  what as an alignment factor, olverly suffices for the 4-byte alignment restriction given a sizeof(wchar_t)==4.
  There's no matter of this if the platforms doesn't impose such a restriction.
  References: posix_memalign function GNU man pages, and
  https://www.gnu.org/software/libc/manual/html_node/iconv-Examples.htm, from which regarded lines are:
  "This looks rather innocent but can lead to problems on platforms that have tight restriction on alignment.
   Therefore the caller of iconv has to make sure that the pointers passed are suitable for access of characters from the appropriate character set." */
  char buf[512], *obuf = (char *)outbuf;
  do {   // read/encoding loop
    char *str_err;
    size_t nread = fread(buf+mblen, 1, sizeof(buf)-mblen, fin);
    if(nread==0) {
      if(mblen!=0) {
        fprintf(stderr, "%s: At End Of File, %s. The error was reported from iconv().\n", __func__, str_err);
        exit(EXIT_FAILURE);    // cluttering
      }
      else
        break;
    }
    mblen += nread;
    size_t wclen = mblen*sizeof(wchar_t);   // edge case each utf8-byte is one char, none of which is a byte-order-mark or alike.
    ptrdiff_t obufoffset = (wchar_t *)obuf-outbuf;
    outbuf = realloc(outbuf, (obufoffset+mblen+1)*sizeof(wchar_t));
    char *ibuf=buf;
    obuf = (char *)(outbuf+obufoffset);
    if(iconv(cd, &ibuf, &mblen, &obuf, &wclen) == (size_t) -1) {
      if (errno == EINVAL) {
        /* This is harmless.  Simply move the unused
           bytes to the beginning of the buffer so that
           they can be used in the next round.  */
        str_err = strerror(errno);
        memmove(buf, ibuf, mblen);
      }
      else {
        /* It is a real problem.  Maybe we ran out of
           space in the output buffer or we have invalid
           input.  In any case back the file pointer to
           the position of the last processed byte.  */
        perror("iconv()");
        exit(EXIT_FAILURE);   // cluttering (refer to headline notes in file containing main()).
      }
    }
  }while(1); // read/encoding loop
  filebufsize = (wchar_t *)obuf-outbuf;
  ptr = filebuf = outbuf;
  iconv_close(cd);
  fclose(fin);
  if(wmemchr(filebuf, L'\0', filebufsize)!=NULL) {
    fprintf(stderr, "Source file %s contains null characters, yet unsupported.", fname); // TODO support but as either escape sequences or universal character names.
    exit(EXIT_FAILURE);
  }
  *(wchar_t *)obuf = L'\0';
  yyparse();
}

static unsigned char *lexbuf;
static size_t lexbuflen, lexbufoff;
static const wchar_t BKSP = L'\b';
static const wchar_t TAB = L'\t';
static const wchar_t LF = L'\n';
static const wchar_t FF = L'\f';
static const wchar_t CR = L'\r';
static const wchar_t QUOT = L'"';
static const wchar_t APOS = L'\'';

int
yylex(void) {
  static const wchar_t STATE1_SET[] = L"\"'();[],.*/{}";   // punctuators, operators, and semi of them (eg. / as operator in its own right and as semi // comment starter), warning on not making right the case of . as fractional separator
  static const wchar_t WSPC[] = L" \n\r\t";   // whitespace
  
  lexbufoff=0;   // restart buffer in case we need it.
  ptr += wcsspn(ptr, WSPC);
  size_t nonstate1_len = wcscspn(ptr, STATE1_SET);
  size_t state1_len = wcsspn(ptr, STATE1_SET);
  size_t nonwspclen = wcscspn(ptr, WSPC);
  size_t state0_len = nonstate1_len<nonwspclen?nonstate1_len:nonwspclen;
  if(state0_len) {
    int toktype = wtname_lookup(ptr, state0_len);
    if(toktype!=-1) {  // it's a language reserved word
      // Create a type descriptor for the type, e.g. L"int" -> L"I"
      for(int i=0; lookup_words[i][0]!=NULL; i++)
        if(wcsncmp(ptr, lookup_words[i][0], state0_len)==0) {
          for_lgwseq((lgwseq_t **)&yylval, 1, lookup_words[i][1]);
	  break;
	}
      ptr += state0_len;
      return toktype;
    }
    // it's a "user-owned" identifier (eg. a variable or a function name), or an integral constant expression
    for_lgwseq((lgwseq_t **)&yylval, state0_len, ptr);
    ptr += state0_len;
    return Identifier;
  }
  else if(state1_len) {
    if(*ptr== L'"'){
      while(*++ptr!=L'"') {
        if(*ptr==L'\\'){
          /* Interprets the following
          EscapeSequence:
          \ b  \u0008 : backspace BS 
          \ t  \u0009 : horizontal tab HT 
          \ n  \u000a : linefeed LF 
          \ f  \u000c : form feed FF 
          \ r  \u000d : carriage return CR 
          \ "  \u0022 : double quote " 
          \ '  \u0027 : single quote ' 
          \ \  \u005c : backslash \ 
          OctalEscape  \u0000 to \u00ff : from octal value 
          OctalEscape:
          \ OctalDigit
          \ OctalDigit OctalDigit
          \ ZeroToThree OctalDigit OctalDigit
          OctalDigit: one of
          0 1 2 3 4 5 6 7
          ZeroToThree: one of
          0 1 2 3
          */
          switch((unsigned char)*(++ptr)) {   // leverages some magic conversion from wchar_t to char, which must be specified somewhere in the ISO-IEC.
            case 'b':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&BKSP, lexbufoff, sizeof(wchar_t))
              break;
            case 't':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&TAB, lexbufoff, sizeof(wchar_t))
              break;
            case 'n':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&LF, lexbufoff, sizeof(wchar_t))
              break;
            case 'f':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&FF, lexbufoff, sizeof(wchar_t))
              break;
            case 'r':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&CR, lexbufoff, sizeof(wchar_t))
              break;
            case '"':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&QUOT, lexbufoff, sizeof(wchar_t))
              break;
            case '\'':
              BUFFER_WRITE(lexbuf, lexbuflen, (void*)&APOS, lexbufoff, sizeof(wchar_t))
              break;
            default:
              yyerror("Invalid escape sequence or yet unsupported octal escape.");  // TODO add octal escape support.
              exit(EXIT_FAILURE);
          }
        }
        else if(*ptr==L'\n' || *ptr==L'\r'){
          yyerror("Line terminator within a string.");
          exit(EXIT_FAILURE); 
        }
        else {
          BUFFER_WRITE(lexbuf, lexbuflen, (void*)ptr,lexbufoff,sizeof(wchar_t))
        }
      }  // while(++ptr!=L'"')
      ptr++;   // wcslen(L"\"")
      for_lgwseq((lgwseq_t**)&yylval, lexbufoff, (wchar_t*)lexbuf);
      return StringLiteral;
    }
    // "//" comments
    if(wcsncmp(ptr, L"//", 2)==0) {
      static wchar_t EOL[] = L"\r\n";
      ptr += wcscspn(ptr, EOL);
      return yylex();
    }

    size_t len=state1_len;
    int toktype;
    while(len!=0 && (toktype=wtname_lookup(ptr, len))==-1)
	    len--;
    ptr += len;
    return toktype;
  }
  else
    return 0;   // End Of input to yyparse() function.
}


struct token_list_st *tok_lst_tmp;
struct token_list_st *token_list(lgwseq_t *token, struct token_list_st *preceeding) {
	tok_lst_tmp = malloc(struct token_list_st);
	tok_lst_tmp->token = token;
	INIT_LIST_HEAD(&tok_lst_tmp->token_list);
	if(preceeding!=NULL)
		list_add(&preceeding->token_list ,&tok_lst_tmp->token_list);
	return tok_lst_tmp;
}

bool token_list_st_equals(struct token_list_st *a, struct token_list_st *b) {
   if(lgwseq_compar(2, a->token, b->token))
     return false;
	struct token_list_st *a_entry, *b_entry = list_peek_entry(struct token_list_st, token_list, &b->token_list);
   list_foreach_entry(a_entry, struct token_list_st, token_list, &a->token_list) {
     if(b_entry==NULL)
       return false;
     if(lgwseq_compar(2, a_entry->token, b_entry->token))
       return false;
     b_entry = list_peek_next_entry(b_entry, struct token_list_st, token_list, &b->token_list);
   }
   if(b_entry!=NULL)
     return false;
   return true;
}

lgwseq_t *strpartcatsep(struct token_list_st *particles, lgwseq_t  *separator) {
  // TODO form an array of pointers to tokens ordered in such way that a ptr to separator is placed between every token, keep element count (let'em be tokary and tokqty)
  return lgwseq_concat(tokqty, tokary);
}

